﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Security.AccessControl;
using System.IO;
using System.Threading;
using System.Runtime.InteropServices;
using System.ComponentModel;
using System.Security.Principal;

namespace HealthMonitoring
{
    internal class MemoryDumpManager
    {

        private MiniDumpType _miniDumpType;
        
        internal string _fileName = null;

        
       
        static EventLog eLog = new EventLog("Application Error");
      
        //exception variable which holds the exception that raised mini dump event
        private Exception _exceptionRaisingMiniDump;
        #region p/Invoke Declarations
        /// <summary>
        /// http://msdn.microsoft.com/en-us/library/ms680360(VS.85).aspx
        /// </summary>
        [DllImport("dbghelp.dll", SetLastError = true)]
        private static extern bool MiniDumpWriteDump(
            IntPtr hProcess, Int32 ProcessId, IntPtr hFile, MiniDumpType DumpType,
            IntPtr ExceptionParam, IntPtr UserStreamParam, IntPtr CallackParam);
        #endregion


        internal MemoryDumpManager(Exception ex)
        {
            //the exception which raised the mini memory dump event
            _exceptionRaisingMiniDump = ex;
            
        }

        #region Methods

        /// <summary>
        /// Checks if current user has access to destination path for writing mini memory dump
        /// If it has access, then check spawnNewThread property and then calls WriteMiniDump method
        /// </summary>

        internal void CreateMiniDump()
        {

            if (MemoryDumpEvent.MemoryDumpConfig == null)
            {
                RaiseDumpCreationFailedException(Constants.ErrorMessageForInvalidMDDSection, MemoryDumpEvent._memoryDumpSectionException, _exceptionRaisingMiniDump);
                return;

            }
            //check for events other than memorydumpevent
            if (!MemoryDumpEvent.MemoryDumpConfig.Enabled)
            {
                return;
            }

            if (!String.IsNullOrEmpty(MemoryDumpEvent.MemoryDumpConfig.ExceptionType))
            {
                if (!MemoryDumpEvent.MemoryDumpConfig.ExceptionType.Equals(_exceptionRaisingMiniDump.GetType().FullName, StringComparison.InvariantCulture))
                {
                    return;
                }
            }
          
            string path = MemoryDumpEvent.MemoryDumpConfig.Path;
            _miniDumpType = MemoryDumpEvent.MemoryDumpConfig.MiniDumpType;



            if (!HasReadandWritePermissions(path, _exceptionRaisingMiniDump))
                return;

            if (MemoryDumpEvent.MemoryDumpConfig.SpawnNewThread)
            {
                try
                {
                    //new thread is created if spawnNewThread property is set to true.
                    Thread mdThread = new Thread(new ParameterizedThreadStart(WriteMiniDump));
                    mdThread.Start(path);
                }


                catch (Exception Ex)
                {
                    // If unable to spawn thread , raise DumpCreationFailedException
                    RaiseDumpCreationFailedException(Constants.ErrorMessageForDumpNotCreated, Ex, _exceptionRaisingMiniDump);

                }
            }
            else
            {
                WriteMiniDump(path);
            }

        }

        private void WriteMiniDump(object data)
        {
            WriteMiniDump(data as string);
        }

        /// <summary>
        /// This function call minidumpwritedump function which writes the mini dump file to specified location.
        /// </summary>
        /// <param name="path"></param>
        private void WriteMiniDump(string path)
        {


            lock (path)
            {
                try
                {
                    FileInfo file;
                    _fileName = string.Format("TestMiniDump-{0}-{1}.dmp", Environment.MachineName, DateTime.Now.ToString("yyyyMMddHHmmssffff"));
                    // write directly to the configured folder
                    file = new FileInfo(Path.Combine(path, _fileName));


                    using (FileStream stream = file.Open(FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None))
                    {
                        Process process = Process.GetCurrentProcess();

                        bool blnWriteSuccess = MiniDumpWriteDump(process.Handle, process.Id, stream.SafeFileHandle.DangerousGetHandle(), _miniDumpType,
                             IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);

                        if (!blnWriteSuccess)
                        {
                            int errorCode = Marshal.GetLastWin32Error();
                            Win32Exception error = new Win32Exception(errorCode);
                            string logErrorMessage = String.Format("{0};ErrorCode: {1};Error Message: {2}", Constants.ErrorMessageForDumpNotCreated, errorCode, error.Message);
                            RaiseDumpCreationFailedException(logErrorMessage, _exceptionRaisingMiniDump);
                        }
                        else
                        { // on success 
                            EventLog.WriteEntry("Application Error", Constants.SuccessMessageForDumpCreated,EventLogEntryType.Information);
                            
                        }
                        stream.SafeFileHandle.Close();
                        stream.Close();

                    }


                }
                catch (Exception Ex)
                {
                    // Raise DumpCreationFailedException
                    RaiseDumpCreationFailedException(Constants.ErrorMessageForDumpNotCreated, Ex, _exceptionRaisingMiniDump);
                    Debug.WriteLine(Ex.ToString());
                }

            }
        }


        /// <summary>
        /// Checks with current user privilages, dump file can be created at specified destination directory or not.
        /// </summary>
        /// <param name="path"></param>
        /// <param name="_exceptionRaisingMiniDump"></param>
        /// <returns></returns>
        private static bool HasReadandWritePermissions(string path, Exception _exceptionRaisingMiniDump)
        {
            DirectoryInfo dumpDir = null;
            try
            {
                dumpDir = new DirectoryInfo(path);
            }
            catch (Exception Ex)
            {
                RaiseDumpCreationFailedException(Constants.ErrorMessageForDumpNotCreated, Ex, _exceptionRaisingMiniDump);
                return false;
            }


            if (dumpDir.Exists)
            {
                string currentUserId = System.Security.Principal.WindowsIdentity.GetCurrent().Name;
                DirectorySecurity accessControlList;
                try
                {
                    accessControlList = Directory.GetAccessControl(path);
                    AuthorizationRuleCollection accessRules = accessControlList.GetAccessRules(true, true, typeof(NTAccount));
                    foreach (FileSystemAccessRule rule in accessRules)
                    {
                        if (rule.IdentityReference.Value == currentUserId)
                        {
                            if ((FileSystemRights.Write & rule.FileSystemRights) != FileSystemRights.Write) continue;

                            if (rule.AccessControlType == AccessControlType.Allow)
                            {
                                break;
                            }
                            else
                            {
                                return false;

                            }
                        }

                    }


                }
                catch (Exception Ex)
                {
                    RaiseDumpCreationFailedException(Constants.ErrorMessageForDumpNotCreated, Ex, _exceptionRaisingMiniDump);
                    return false;
                }
            }

            else
            {
                try
                {
                    dumpDir.Create();

                }
                catch (Exception Ex)
                {
                    RaiseDumpCreationFailedException(Constants.ErrorMessageForDumpNotCreated, Ex, _exceptionRaisingMiniDump);
                    return false;
                }

            }

            return true;
        }

        /// <summary>
        ///  Raises the ErrorMessageEvent for DumpCreationFailed Exception
        /// </summary>
        /// <param name="errMessage"></param>
        /// <param name="Ex"></param>
        /// <param name="_exceptionRaisingMiniDump"></param>
        internal static void RaiseDumpCreationFailedException(string errMessage, Exception Ex, Exception _exceptionRaisingMiniDump)
        {
            string customErrorMsg = String.Format("{0}; MiniDump creation raised by:ExceptionType:{1}, Exception Message:{2}", errMessage, _exceptionRaisingMiniDump.GetType().FullName, _exceptionRaisingMiniDump.Message);


            EventLog.WriteEntry("Application Error", customErrorMsg,EventLogEntryType.Warning,1);
           


          

        }

        /// <summary>
        /// Raises the ErrorMessageEvent for DumpCreationFailed Exception
        /// </summary>
        /// <param name="errMessage"></param>
        /// <param name="_exceptionRaisingMiniDump"></param>
        internal static void RaiseDumpCreationFailedException(string errMessage, Exception _exceptionRaisingMiniDump)
        {
            RaiseDumpCreationFailedException(errMessage, null, _exceptionRaisingMiniDump);

        }

        #endregion


        internal static class Constants
        {
            public const string Source = "Application";

            public const string ErrorMessageForDumpNotCreated = "Unable to write Minidump";
            public const string ErrorMessageForInvalidMDDSection = "Unable to write Minidump because either memoryDumpDiagnostics section is not present in config or required property Path is not set";
            public const string SuccessMessageForDumpCreated = "MiniMemoryDump created successfully";
            public const string WarningForUnsupportedEventtype = "unsupported event type";
            public const string MemoryDumpDiagnosticsSectionName = "memoryDumpDiagnostics";
            public const string MiniDumpTaken = "MiniDumpTaken";
            public const int DefaultOffSetCode = 10;

        }
    }

    

}



